﻿using System;
using System.Collections.Generic;
using System.Text;

namespace SharpSoft.Geometries.Shapes
{
    public class TransformTool
    {
        internal TransformTool(IShape shape)
        {

        }
        /// <summary>
        /// 使用工具框矩形初始化变换工具
        /// </summary>
        /// <param name="rect"></param>
        public TransformTool(Rect rect)
        {
            this.P1 = new Point2D(rect.X, rect.Y);

            this.P3 = new Point2D(rect.X + rect.Width, rect.Y + rect.Height);

            this.P2 = new Point2D(this.P3.X, this.P1.Y);

            this.P4 = new Point2D(this.P1.X, this.P3.Y);

        }
        /// <summary>
        /// 使用多边形顶点初始化变化工具
        /// </summary>
        /// <param name="plygon"></param>
        public TransformTool(Point2D[] polygon)
        {
            this.SetPolygon(polygon);
        }

        /// <summary>
        /// 第一个手柄的位置（初始矩形的左上角）
        /// </summary>
        public Point2D P1 { get; private set; }
        /// <summary>
        /// 第二个手柄的位置（初始矩形的右上角）
        /// </summary>
        public Point2D P2 { get; private set; }
        /// <summary>
        /// 第三个手柄的位置（初始矩形的右下角）
        /// </summary>
        public Point2D P3 { get; private set; }
        /// <summary>
        /// 第四个手柄的位置（初始矩形的左下角）
        /// </summary>
        public Point2D P4 { get; private set; }
        /// <summary>
        /// 旋转中心
        /// </summary>
        public Point2D RotationCenter
        {
            get
            {
                return GetCenter();
            }
        }
        /// <summary>
        /// 设置是否允许旋转操作
        /// </summary>
        public bool AllowRotation { get; set; } = true;

        /// <summary>
        /// 中心点(计算外接正矩形的中心点)
        /// </summary>
        public Point2D GetCenter()
        {
            var p1 = transform.Transform(this.P1);
            var p2 = transform.Transform(this.P2);
            var p3 = transform.Transform(this.P3);
            var p4 = transform.Transform(this.P4);
            var minx = Math.Min(Math.Min(p1.X, p2.X), Math.Min(p3.X, p4.X));
            var maxx = Math.Max(Math.Max(p1.X, p2.X), Math.Max(p3.X, p4.X));

            var miny = Math.Min(Math.Min(p1.Y, p2.Y), Math.Min(p3.Y, p4.Y));
            var maxy = Math.Max(Math.Max(p1.Y, p2.Y), Math.Max(p3.Y, p4.Y));

            return new Point2D(minx + (maxx - minx) / 2, miny + (maxy - miny) / 2);
        }

        /// <summary>
        /// 获取或设置手柄的宽度
        /// </summary>
        public double HandleWidth { get; set; } = 12;

        private Transform2D transform = new Transform2D();
        /// <summary>
        /// 经操作后的变换
        /// </summary>
        public Transform2D Transform { get => transform; private set => transform = value; }

        /// <summary>
        /// 设置当前工具操作的多边形
        /// </summary>
        /// <param name="polygon"></param>
        public void SetPolygon(Point2D[] polygon)
        {
            if (polygon == null || polygon.Length == 0)
            {
                throw new Exception("顶点不能为空。");
            }
            double minx = polygon[0].X, maxx = polygon[0].X, miny = polygon[0].Y, maxy = polygon[0].Y;
            foreach (var p in polygon)
            {
                minx = Math.Min(p.X, minx);
                miny = Math.Min(p.Y, miny);
                maxx = Math.Max(p.X, maxx);
                maxy = Math.Max(p.Y, maxy);
            }
            this.P1 = new Point2D(minx, miny);
            this.P2 = new Point2D(maxx, miny);
            this.P3 = new Point2D(maxx, maxy);
            this.P4 = new Point2D(minx, maxy);
        }

        //当前正在执行的操作
        private HitTestTarget action_status = HitTestTarget.Empty;
        private Point2D lastpoint;
        /// <summary>
        /// 从指定的位置开始执行操作
        /// </summary>
        /// <param name="p"></param>
        public void StartAction(Point2D p)
        {
            var result = HitTest(p);
            action_status = result.Target;
            lastpoint = p;
        }
        /// <summary>
        /// 操作到新的位置
        /// </summary>
        /// <param name="p"></param>
        public void DoAction(Point2D p)
        {
            Vector2D v = p - lastpoint;//此次操作的向量
            var rc = this.RotationCenter;
            Vector2D v1 = lastpoint - rc;
            Vector2D v2 = p - rc;
            lastpoint = p;
            var p1 = Transform.Transform(this.P1);
            var p2 = Transform.Transform(this.P2);
            var p3 = Transform.Transform(this.P3);
            var p4 = Transform.Transform(this.P4);

            var v_p1_p2 = p1 - p2;
            var v_p1_p3 = p1 - p3;
            var v_p2_p1 = p2 - p1;
            var v_p2_p3 = p2 - p3;
            var v_p3_p2 = p3 - p2;
            var v_p3_p4 = p3 - p4;
            var v_p4_p3 = p4 - p3;
            var v_p4_p1 = p4 - p1;

            switch (action_status)
            {
                case HitTestTarget.Empty:
                    break;
                case HitTestTarget.Center:
                case HitTestTarget.InnerSpace://平移
                    transform.Translate(v);
                    break;
                case HitTestTarget.P1://沿P1-P3缩放
                    //var v_p1_p3 = p3 - p1;
                    //var nv1 = Vector2D.Projection(v_p1_p3, v);
                    //var sv1 = v_p1_p3 - nv1;
                    //transform.ScaleAt(sv1.X / v_p1_p3.X, sv1.Y / v_p1_p3.Y, p3.X, p3.Y);

                    transform.ScaleAt((v_p1_p2.X + v.X) / v_p1_p2.X, (v_p1_p3.Y + v.Y) / v_p1_p3.Y, p3.X, p3.Y);
                    break;
                case HitTestTarget.P2://沿P2-P4缩放
                    //var v_p2_p4 = p4 - p2;
                    //var nv2 = Vector2D.Projection(v_p2_p4, v);
                    //var sv2 = v_p2_p4 - nv2;
                    //transform.ScaleAt(sv2.X / v_p2_p4.X, sv2.Y / v_p2_p4.Y, p4.X, p4.Y);

                    transform.ScaleAt((v_p2_p1.X + v.X) / v_p2_p1.X, (v_p2_p3.Y + v.Y) / v_p2_p3.Y, p4.X, p4.Y);
                    break;
                case HitTestTarget.P3://沿P3-P1缩放
                    //var v_p3_p1 = p1 - p3;
                    //var nv3 = Vector2D.Projection(v_p3_p1, v);
                    //var sv3 = v_p3_p1 - nv3;
                    //transform.ScaleAt(sv3.X / v_p3_p1.X, sv3.Y / v_p3_p1.Y, p1.X, p1.Y);

                    transform.ScaleAt((v_p3_p4.X + v.X) / v_p3_p4.X, (v_p3_p2.Y + v.Y) / v_p3_p2.Y, p1.X, p1.Y);
                    break;
                case HitTestTarget.P4://沿P4-P2缩放
                    //var v_p4_p2 = p2 - p4;
                    //var nv4 = Vector2D.Projection(v_p4_p2, v);
                    //var sv4 = v_p4_p2 - nv4;
                    //transform.ScaleAt(sv4.X / v_p4_p2.X, sv4.Y / v_p4_p2.Y, p2.X, p2.Y);

                    transform.ScaleAt((v_p4_p3.X + v.X) / v_p4_p3.X, (v_p4_p1.Y + v.Y) / v_p4_p1.Y, p2.X, p2.Y);
                    break;
                case HitTestTarget.P1_P2://操作P1-P2边缩放
                    var v_p1_p4 = p4 - p1;
                    var nv5 = Vector2D.Projection(v_p1_p4, v);
                    var sv5 = v_p1_p4 - nv5;
                    transform.ScaleAt(1, sv5.Y / v_p1_p4.Y, p4.X, p4.Y);
                    break;
                case HitTestTarget.P2_P3:
                    var nv6 = Vector2D.Projection(v_p2_p1, v);
                    var sv6 = v_p2_p1 - nv6;
                    transform.ScaleAt(sv6.X / v_p2_p1.X, 1, p1.X, p1.Y);
                    break;
                case HitTestTarget.P3_P4:
                    var nv7 = Vector2D.Projection(v_p2_p3, v);
                    var sv7 = v_p2_p3 - nv7;
                    transform.ScaleAt(1, sv7.Y / v_p2_p3.Y, p2.X, p2.Y);
                    break;
                case HitTestTarget.P4_P1:
                    var nv8 = Vector2D.Projection(v_p3_p4, v);
                    var sv8 = v_p3_p4 - nv8;
                    transform.ScaleAt(sv8.X / v_p3_p4.X, 1, p3.X, p3.Y);
                    break;
                case HitTestTarget.Outskirts:
                    var r = Vector2D.AngleBetween(v1, v2);
                    transform.RotationAt(r, rc.X, rc.Y);
                    break;
                default:
                    break;
            }
        }
        /// <summary>
        /// 结束操作
        /// </summary>
        /// <param name="p"></param>
        public void EndAction(Point2D p)
        {
            action_status = HitTestTarget.Empty;
            lastpoint = new Point2D(0, 0);
            var p1 = transform.Transform(this.P1);
            var p2 = transform.Transform(this.P2);
            var p3 = transform.Transform(this.P3);
            var p4 = transform.Transform(this.P4);

            transform = new Transform2D();
            SetPolygon(new Point2D[] { p1, p2, p3, p4 });
            //p1 = transform.Transform(this.P1);
            //p2 = transform.Transform(this.P2);
            //p3 = transform.Transform(this.P3);
            //p4 = transform.Transform(this.P4);
        }

        /// <summary>
        /// 对当前变换工具进行击中测试
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        public HitTestResult HitTest(Point2D p)
        {
            HitTestTarget target = HitTestTarget.Empty;
            var p1 = Transform.Transform(this.P1);
            var p2 = Transform.Transform(this.P2);
            var p3 = Transform.Transform(this.P3);
            var p4 = Transform.Transform(this.P4);
            var center = this.GetCenter();

            if (Line2D.GetLength(p1, p) <= this.HandleWidth)
            {
                target = HitTestTarget.P1;
            }
            else if (Line2D.GetLength(p2, p) <= this.HandleWidth)
            {
                target = HitTestTarget.P2;
            }
            else if (Line2D.GetLength(p3, p) <= this.HandleWidth)
            {
                target = HitTestTarget.P3;
            }
            else if (Line2D.GetLength(p4, p) <= this.HandleWidth)
            {
                target = HitTestTarget.P4;
            }
            else
            {
                if (GeometryHelper.IsPointInPolygon(p, new Point2D[] { p1, p2, p3, p4 }))
                {//点在内部
                    if (Line2D.GetLength(center, p) <= this.HandleWidth)
                    {
                        target = HitTestTarget.Center;
                    }
                    else
                    {
                        target = HitTestTarget.InnerSpace;
                    }
                }
                else
                {//点在外部

                    //计算点到边线的距离是否在指定范围内
                    if (Line2D.GetMinDistanse(p1, p2, p) <= HandleWidth)
                    {
                        target = HitTestTarget.P1_P2;
                    }
                    else if (Line2D.GetMinDistanse(p2, p3, p) <= HandleWidth)
                    {
                        target = HitTestTarget.P2_P3;
                    }
                    else if (Line2D.GetMinDistanse(p3, p4, p) <= HandleWidth)
                    {
                        target = HitTestTarget.P3_P4;
                    }
                    else if (Line2D.GetMinDistanse(p4, p1, p) <= HandleWidth)
                    {
                        target = HitTestTarget.P4_P1;
                    }
                    else
                    {//点与四边的距离均不在指定范围内。
                        //测试是否在双倍距离内（旋转操作范围）
                        var d = HandleWidth * 2;
                        if (
                            AllowRotation && (
                            Line2D.GetMinDistanse(p1, p2, p) <= d
                            || Line2D.GetMinDistanse(p2, p3, p) <= d
                            || Line2D.GetMinDistanse(p3, p4, p) <= d
                            || Line2D.GetMinDistanse(p4, p1, p) <= d
                            ))
                        {
                            target = HitTestTarget.Outskirts;
                        }
                        else
                        {
                            target = HitTestTarget.Empty;
                        }

                    }
                }

            }

            var y = p.Y - center.Y;
            var x = p.X - center.X;
            var angle = Math.Atan2(y, x);

            HitTestResult result = new HitTestResult() { Target = target, Azimuth = new Radian(angle) };
            return result;
        }
        //获取手柄所在的矩形
        private Rect getHandleRect(Point2D p)
        {
            return new Rect(p - new Vector2D(HandleWidth / 2, HandleWidth / 2), new Size2D(HandleWidth, HandleWidth));
        }
        /// <summary>
        /// 击中测试结果
        /// </summary>
        public struct HitTestResult
        {
            /// <summary>
            /// 击中目标
            /// </summary>
            public HitTestTarget Target { get; set; }
            /// <summary>
            /// 中心点到测试点的方位角
            /// </summary>
            public Radian Azimuth { get; set; }
        }

        /// <summary>
        /// 击中测试的目标
        /// </summary>
        public enum HitTestTarget
        {
            /// <summary>
            /// 未击中任何目标
            /// </summary>
            Empty = 0,
            /// <summary>
            /// 中心位置
            /// </summary>
            Center,
            /// <summary>
            /// 内部空白(用于平移操作)
            /// </summary>
            InnerSpace,
            /// <summary>
            /// 第一个把手
            /// </summary>
            P1,
            /// <summary>
            /// 第二个把手
            /// </summary>
            P2,
            /// <summary>
            /// 第三个把手
            /// </summary>
            P3,
            /// <summary>
            /// 第四个把手
            /// </summary>
            P4,
            /// <summary>
            /// 线段P1-P2
            /// </summary>
            P1_P2,
            /// <summary>
            /// 线段P2-P3
            /// </summary>
            P2_P3,
            /// <summary>
            /// 线段P3-P4
            /// </summary>
            P3_P4,
            /// <summary>
            /// 线段P4-P1
            /// </summary>
            P4_P1,
            /// <summary>
            /// 外围区域（用于旋转）
            /// </summary>
            Outskirts
        }

    }
}
